#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
macOS 配置文件备份和恢复工具
支持备份所有描述文件为 .mobileconfig 格式，并可选择性恢复
"""

import os
import sys
import json
import subprocess
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional


class ProfileManager:
    """macOS 配置文件管理器"""

    def __init__(self, backup_base_dir: str = None):
        """
        初始化配置文件管理器

        Args:
            backup_base_dir: 备份目录基路径，默认为当前目录下的 profile_backups
        """
        if backup_base_dir is None:
            backup_base_dir = os.path.join(
                os.path.dirname(os.path.abspath(__file__)), "profile_backups"
            )

        self.backup_base_dir = Path(backup_base_dir)

        # 尝试创建备份目录，处理权限问题
        self._ensure_backup_directory()

        # 配置日志
        self._setup_logging()

    def _setup_logging(self):
        """设置日志配置"""
        log_file = self.backup_base_dir / "profile_manager.log"

        handlers = [logging.StreamHandler(sys.stdout)]

        # 尝试添加文件处理器，如果失败则只使用控制台输出
        try:
            handlers.append(logging.FileHandler(log_file, encoding="utf-8"))
        except PermissionError:
            print(f"警告: 无法创建日志文件 {log_file}，将只输出到控制台")

        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s - %(levelname)s - %(message)s",
            handlers=handlers,
        )
        self.logger = logging.getLogger(__name__)

    def _ensure_backup_directory(self):
        """确保备份目录存在且具有正确的权限"""
        try:
            # 尝试创建目录
            self.backup_base_dir.mkdir(exist_ok=True)

            # 测试写入权限
            test_file = self.backup_base_dir / ".permission_test"
            try:
                test_file.touch()
                test_file.unlink()  # 删除测试文件
            except PermissionError:
                # 如果没有写入权限，尝试修复权限
                print(f"警告: 备份目录 {self.backup_base_dir} 权限不足")
                if os.geteuid() == 0:  # 如果是root用户
                    # 修改目录权限为当前用户可读写
                    import pwd

                    original_user = os.environ.get("SUDO_USER")
                    if original_user:
                        user_info = pwd.getpwnam(original_user)
                        os.chown(
                            self.backup_base_dir, user_info.pw_uid, user_info.pw_gid
                        )
                        os.chmod(self.backup_base_dir, 0o755)
                        print(f"已修复备份目录权限")
                else:
                    print(f"请使用 sudo 运行此脚本以获得必要权限")

        except PermissionError as e:
            print(f"错误: 无法创建备份目录 {self.backup_base_dir}: {e}")
            print("请使用 sudo 运行此脚本：sudo python3 profile_manager.py")
            raise
        except Exception as e:
            print(f"创建备份目录时出现意外错误: {e}")
            raise

    def _run_command(self, command: List[str], capture_output: bool = True) -> tuple:
        """
        执行系统命令

        Args:
            command: 命令列表
            capture_output: 是否捕获输出

        Returns:
            (returncode, stdout, stderr)
        """
        try:
            self.logger.debug(f"执行命令: {' '.join(command)}")
            result = subprocess.run(
                command, capture_output=capture_output, text=True, check=False
            )
            return result.returncode, result.stdout, result.stderr
        except Exception as e:
            self.logger.error(f"命令执行失败: {e}")
            return 1, "", str(e)

    def get_all_profiles(self) -> List[Dict[str, str]]:
        """
        获取系统中所有配置文件的列表

        Returns:
            配置文件信息列表
        """
        self.logger.info("正在获取系统配置文件列表...")

        # 获取配置文件列表
        code, stdout, stderr = self._run_command(["sudo", "profiles", "show"])

        if code != 0:
            self.logger.error(f"获取配置文件列表失败: {stderr}")
            return []

        profiles = []
        lines = stdout.split("\n")
        current_profile = {}

        for line in lines:
            line = line.strip()
            # 适配实际的输出格式：attribute: profileIdentifier: xxx
            if "attribute: profileIdentifier:" in line:
                # 保存上一个配置文件
                if current_profile and "identifier" in current_profile:
                    profiles.append(current_profile)
                # 开始新的配置文件
                identifier = line.split("attribute: profileIdentifier:", 1)[1].strip()
                current_profile = {"identifier": identifier}
            elif "attribute: name:" in line and current_profile:
                name = line.split("attribute: name:", 1)[1].strip()
                current_profile["name"] = name
            elif "attribute: version:" in line and current_profile:
                version = line.split("attribute: version:", 1)[1].strip()
                current_profile["version"] = version
            elif "attribute: profileUUID:" in line and current_profile:
                uuid = line.split("attribute: profileUUID:", 1)[1].strip()
                current_profile["uuid"] = uuid

        # 添加最后一个配置文件
        if current_profile and "identifier" in current_profile:
            profiles.append(current_profile)

        self.logger.info(f"找到 {len(profiles)} 个配置文件")
        return profiles

    def backup_all_profiles(self) -> str:
        """
        备份所有配置文件

        Returns:
            备份目录路径
        """
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_dir = self.backup_base_dir / f"backup_{timestamp}"

        try:
            backup_dir.mkdir(exist_ok=True)
        except PermissionError:
            print(f"错误: 无法创建备份目录 {backup_dir}")
            print("请使用 sudo 运行此脚本：sudo python3 profile_manager.py")
            raise

        self.logger.info(f"开始备份所有配置文件到: {backup_dir}")

        profiles = self.get_all_profiles()
        if not profiles:
            self.logger.warning("未找到任何配置文件")
            return str(backup_dir)

        successful_backups = 0
        failed_backups = 0
        backup_info = {
            "timestamp": timestamp,
            "total_profiles": len(profiles),
            "successful_backups": 0,
            "failed_backups": 0,
            "profiles": [],
        }

        for i, profile in enumerate(profiles, 1):
            identifier = profile["identifier"]
            name = profile.get("name", identifier)

            print(f"[{i}/{len(profiles)}] 正在备份: {name}")
            self.logger.info(f"备份配置文件: {identifier}")

            # 生成安全的文件名
            safe_filename = self._make_safe_filename(identifier) + ".mobileconfig"
            output_path = backup_dir / safe_filename

            # 备份配置文件
            code, stdout, stderr = self._run_command(
                [
                    "sudo",
                    "profiles",
                    "show",
                    "-type",
                    "configuration",
                    "-identifier",
                    identifier,
                    "-output",
                    str(output_path),
                ]
            )

            if code == 0 and output_path.exists():
                successful_backups += 1
                profile["backup_file"] = safe_filename
                profile["backup_status"] = "success"
                self.logger.info(f"成功备份: {identifier}")
            else:
                failed_backups += 1
                profile["backup_status"] = "failed"
                profile["error"] = stderr
                self.logger.error(f"备份失败 {identifier}: {stderr}")

            backup_info["profiles"].append(profile)

        backup_info["successful_backups"] = successful_backups
        backup_info["failed_backups"] = failed_backups

        # 保存备份信息
        info_file = backup_dir / "backup_info.json"
        with open(info_file, "w", encoding="utf-8") as f:
            json.dump(backup_info, f, ensure_ascii=False, indent=2)

        print(f"\n备份完成!")
        print(f"成功备份: {successful_backups} 个")
        print(f"备份失败: {failed_backups} 个")
        print(f"备份目录: {backup_dir}")

        return str(backup_dir)

    def _make_safe_filename(self, filename: str) -> str:
        """
        将字符串转换为安全的文件名

        Args:
            filename: 原始文件名

        Returns:
            安全的文件名
        """
        # 替换不安全的字符
        unsafe_chars = '<>:"/\\|?*'
        safe_filename = filename
        for char in unsafe_chars:
            safe_filename = safe_filename.replace(char, "_")

        # 限制长度
        if len(safe_filename) > 200:
            safe_filename = safe_filename[:200]

        return safe_filename

    def list_backup_directories(self) -> List[str]:
        """
        列出所有备份目录

        Returns:
            备份目录列表
        """
        backup_dirs = []
        if self.backup_base_dir.exists():
            for item in self.backup_base_dir.iterdir():
                if item.is_dir() and item.name.startswith("backup_"):
                    backup_dirs.append(str(item))

        backup_dirs.sort(reverse=True)  # 最新的在前面
        return backup_dirs

    def get_backup_info(self, backup_dir: str) -> Optional[Dict]:
        """
        获取备份信息

        Args:
            backup_dir: 备份目录路径

        Returns:
            备份信息字典
        """
        info_file = Path(backup_dir) / "backup_info.json"
        if not info_file.exists():
            return None

        try:
            with open(info_file, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception as e:
            self.logger.error(f"读取备份信息失败: {e}")
            return None

    def restore_profile(self, backup_dir: str, profile_identifier: str) -> bool:
        """
        恢复单个配置文件

        Args:
            backup_dir: 备份目录路径
            profile_identifier: 配置文件标识符

        Returns:
            是否成功
        """
        backup_info = self.get_backup_info(backup_dir)
        if not backup_info:
            self.logger.error("无法读取备份信息")
            return False

        # 查找配置文件
        target_profile = None
        for profile in backup_info["profiles"]:
            if profile["identifier"] == profile_identifier:
                target_profile = profile
                break

        if not target_profile:
            self.logger.error(f"未找到配置文件: {profile_identifier}")
            return False

        if target_profile.get("backup_status") != "success":
            self.logger.error(f"配置文件备份不完整: {profile_identifier}")
            return False

        backup_file = Path(backup_dir) / target_profile["backup_file"]
        if not backup_file.exists():
            self.logger.error(f"备份文件不存在: {backup_file}")
            return False

        self.logger.info(f"正在恢复配置文件: {profile_identifier}")

        # 尝试安装配置文件（新版 macOS 可能不支持）
        code, stdout, stderr = self._run_command(
            ["sudo", "profiles", "install", "-path", str(backup_file)]
        )

        # 检查是否因为新版 macOS 而失败
        if code != 0 and (
            "no longer supports" in stderr or "no longer supports" in stdout
        ):
            print(f"\n⚠️  注意: 您的 macOS 版本不再支持命令行安装配置文件")
            print(f"请手动安装配置文件:")
            print(f"  1. 双击文件: {backup_file}")
            print(f"  2. 或者打开 '系统设置' > '隐私与安全性' > '描述文件'")
            print(f"  3. 然后手动安装该配置文件\n")
            self.logger.warning(f"需要手动安装配置文件: {backup_file}")

            # 尝试用 open 命令打开文件
            try:
                subprocess.run(["open", str(backup_file)], check=False)
                print(f"✓ 已自动打开配置文件，请在弹出的窗口中点击安装")
                return True
            except Exception as e:
                self.logger.error(f"无法打开配置文件: {e}")
                return False

        if code == 0:
            self.logger.info(f"成功恢复配置文件: {profile_identifier}")
            return True
        else:
            error_msg = stderr if stderr else stdout if stdout else "未知错误"
            self.logger.error(f"恢复配置文件失败 {profile_identifier}: {error_msg}")
            print(f"  错误详情: {error_msg}")
            return False

    def restore_all_profiles(self, backup_dir: str) -> Dict[str, int]:
        """
        恢复所有配置文件

        Args:
            backup_dir: 备份目录路径

        Returns:
            恢复结果统计
        """
        backup_info = self.get_backup_info(backup_dir)
        if not backup_info:
            self.logger.error("无法读取备份信息")
            return {"successful": 0, "failed": 0, "total": 0}

        successful_profiles = [
            p for p in backup_info["profiles"] if p.get("backup_status") == "success"
        ]
        total_count = len(successful_profiles)
        successful_count = 0
        failed_count = 0

        print(f"开始恢复 {total_count} 个配置文件...")

        for i, profile in enumerate(successful_profiles, 1):
            identifier = profile["identifier"]
            name = profile.get("name", identifier)

            print(f"[{i}/{total_count}] 正在恢复: {name}")

            if self.restore_profile(backup_dir, identifier):
                successful_count += 1
            else:
                failed_count += 1

        print(f"\n恢复完成!")
        print(f"成功恢复: {successful_count} 个")
        print(f"恢复失败: {failed_count} 个")

        return {
            "successful": successful_count,
            "failed": failed_count,
            "total": total_count,
        }

    def remove_profile(self, profile_identifier: str) -> bool:
        """
        删除单个配置文件

        Args:
            profile_identifier: 配置文件标识符

        Returns:
            是否成功
        """
        self.logger.info(f"正在删除配置文件: {profile_identifier}")

        # 删除配置文件
        code, stdout, stderr = self._run_command(
            ["sudo", "profiles", "remove", "-identifier", profile_identifier]
        )

        if code == 0:
            self.logger.info(f"成功删除配置文件: {profile_identifier}")
            return True
        else:
            error_msg = stderr if stderr else stdout if stdout else "未知错误"
            self.logger.error(f"删除配置文件失败 {profile_identifier}: {error_msg}")
            print(f"  错误详情: {error_msg}")
            return False

    def remove_all_profiles(self) -> Dict[str, int]:
        """
        删除所有配置文件

        Returns:
            删除结果统计
        """
        self.logger.info("开始删除所有配置文件...")

        profiles = self.get_all_profiles()
        if not profiles:
            self.logger.warning("未找到任何配置文件")
            return {"successful": 0, "failed": 0, "total": 0}

        total_count = len(profiles)
        successful_count = 0
        failed_count = 0

        print(f"开始删除 {total_count} 个配置文件...")

        for i, profile in enumerate(profiles, 1):
            identifier = profile["identifier"]
            name = profile.get("name", identifier)

            print(f"[{i}/{total_count}] 正在删除: {name}")

            if self.remove_profile(identifier):
                successful_count += 1
            else:
                failed_count += 1

        print(f"\n删除完成!")
        print(f"成功删除: {successful_count} 个")
        print(f"删除失败: {failed_count} 个")

        return {
            "successful": successful_count,
            "failed": failed_count,
            "total": total_count,
        }


def main():
    """主函数"""
    print("=" * 60)
    print("       macOS 配置文件备份和恢复工具")
    print("=" * 60)
    print()
    print("⚠️  重要说明:")
    print("- 此工具主要用于备份和查看配置文件内容")
    print("- 由 MDM（如 Jamf）管理的配置文件无法通过此工具恢复")
    print("- MDM 配置文件删除后需要通过 MDM 服务器重新推送")
    print("=" * 60)

    # 检查系统兼容性
    if sys.platform != "darwin":
        print("❌ 此工具只能在 macOS 系统上运行")
        sys.exit(1)

    # 检查是否需要提醒用户使用 sudo
    if os.geteuid() != 0:
        print("⚠️  注意: 大部分功能需要管理员权限")
        print("建议使用: sudo python3 profile_manager.py")
        print("您仍可以查看现有备份，但无法创建新备份或恢复配置文件\n")

    try:
        manager = ProfileManager()
    except Exception as e:
        print(f"初始化失败: {e}")
        sys.exit(1)

    while True:
        print("\n请选择操作:")
        print("1. 备份所有配置文件")
        print("2. 查看备份列表")
        print("3. 恢复配置文件")
        print("4. 删除所有配置文件")
        print("5. 退出")

        choice = input("\n请输入选择 (1-5): ").strip()

        if choice == "1":
            print("\n开始备份配置文件...")
            backup_dir = manager.backup_all_profiles()

        elif choice == "2":
            backup_dirs = manager.list_backup_directories()
            if not backup_dirs:
                print("未找到任何备份")
                continue

            print(f"\n找到 {len(backup_dirs)} 个备份:")
            for i, backup_dir in enumerate(backup_dirs, 1):
                info = manager.get_backup_info(backup_dir)
                if info:
                    timestamp = info["timestamp"]
                    total = info["total_profiles"]
                    successful = info["successful_backups"]
                    print(f"{i}. {timestamp} - 总计: {total}, 成功: {successful}")
                else:
                    print(f"{i}. {Path(backup_dir).name} - 信息缺失")

        elif choice == "3":
            backup_dirs = manager.list_backup_directories()
            if not backup_dirs:
                print("未找到任何备份")
                continue

            # 选择备份
            print(f"\n请选择要恢复的备份:")
            for i, backup_dir in enumerate(backup_dirs, 1):
                info = manager.get_backup_info(backup_dir)
                if info:
                    timestamp = info["timestamp"]
                    total = info["total_profiles"]
                    successful = info["successful_backups"]
                    print(f"{i}. {timestamp} - 总计: {total}, 成功: {successful}")
                else:
                    print(f"{i}. {Path(backup_dir).name} - 信息缺失")

            try:
                backup_choice = (
                    int(input(f"\n请输入备份编号 (1-{len(backup_dirs)}): ")) - 1
                )
                if backup_choice < 0 or backup_choice >= len(backup_dirs):
                    print("无效的选择")
                    continue

                selected_backup = backup_dirs[backup_choice]
                backup_info = manager.get_backup_info(selected_backup)

                if not backup_info:
                    print("无法读取备份信息")
                    continue

                # 选择恢复方式
                print(f"\n请选择恢复方式:")
                print("1. 恢复所有配置文件")
                print("2. 选择特定配置文件恢复")

                restore_choice = input("请输入选择 (1-2): ").strip()

                if restore_choice == "1":
                    # 恢复所有
                    print("\n⚠️  警告: 这将恢复所有备份的配置文件到系统中")
                    confirm = input("确认继续? (y/N): ").strip().lower()
                    if confirm == "y":
                        manager.restore_all_profiles(selected_backup)
                    else:
                        print("操作已取消")

                elif restore_choice == "2":
                    # 选择特定文件
                    successful_profiles = [
                        p
                        for p in backup_info["profiles"]
                        if p.get("backup_status") == "success"
                    ]

                    if not successful_profiles:
                        print("该备份中没有可恢复的配置文件")
                        continue

                    print(f"\n可恢复的配置文件:")
                    for i, profile in enumerate(successful_profiles, 1):
                        name = profile.get("name", profile["identifier"])
                        print(f"{i}. {name} ({profile['identifier']})")

                    try:
                        profile_choice = (
                            int(
                                input(
                                    f"\n请输入配置文件编号 (1-{len(successful_profiles)}): "
                                )
                            )
                            - 1
                        )
                        if profile_choice < 0 or profile_choice >= len(
                            successful_profiles
                        ):
                            print("无效的选择")
                            continue

                        selected_profile = successful_profiles[profile_choice]

                        print(
                            f"\n⚠️  警告: 这将恢复配置文件 '{selected_profile.get('name', selected_profile['identifier'])}' 到系统中"
                        )
                        confirm = input("确认继续? (y/N): ").strip().lower()
                        if confirm == "y":
                            if manager.restore_profile(
                                selected_backup, selected_profile["identifier"]
                            ):
                                print("恢复成功!")
                            else:
                                print("恢复失败!")
                        else:
                            print("操作已取消")

                    except ValueError:
                        print("请输入有效的数字")
                        continue

                else:
                    print("无效的选择")
                    continue

            except ValueError:
                print("请输入有效的数字")
                continue

        elif choice == "4":
            # 删除所有配置文件
            profiles = manager.get_all_profiles()
            if not profiles:
                print("系统中没有任何配置文件")
                continue

            print(f"\n⚠️  警告: 发现 {len(profiles)} 个配置文件:")
            for i, profile in enumerate(profiles, 1):
                name = profile.get("name", profile["identifier"])
                print(f"  {i}. {name}")

            print("\n⚠️  危险操作: 这将删除系统中的所有配置文件!")
            print("建议在删除前先执行备份操作")
            confirm = input("\n确认删除所有配置文件? (输入 'DELETE' 确认): ").strip()

            if confirm == "DELETE":
                result = manager.remove_all_profiles()
            else:
                print("操作已取消")

        elif choice == "5":
            print("再见!")
            break

        else:
            print("无效的选择，请重新输入")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n操作被用户中断")
        sys.exit(1)
    except Exception as e:
        print(f"\n程序出现错误: {e}")
        sys.exit(1)
